/****************************************************************************
 * Copyright (c) 2004, 2007 Composent, Inc. and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Composent, Inc. - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package org.eclipse.ecf.core;

import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.provider.IContainerInstantiator;
import org.eclipse.ecf.core.util.Trace;
import org.eclipse.ecf.internal.core.*;

/**
 * Factory for creating {@link IContainer} instances. This class provides ECF
 * clients an entry point to constructing {@link IContainer} instances. <br>
 * <br>
 * Here is an example use of the ContainerFactory to construct an instance of
 * the 'standalone' container (has no connection to other containers): <br>
 * <br>
 * <code>
 * 	    IContainer container = <br>
 * 			ContainerFactory.getDefault().createContainer("ecf.generic.client");
 *      <br><br>
 *      ...further use of container here...
 * </code> For more details on the creation
 * and lifecycle of IContainer instances created via this factory see
 * {@link IContainer}.
 * 
 * @see IContainer
 * @see IContainerFactory
 * @since 3.1
 */
public class ContainerFactory implements IContainerFactory, IContainerManager {

	public static final String BASE_CONTAINER_NAME = "ecf.base"; //$NON-NLS-1$

	static final Map containerdescriptions = new HashMap();

	static final Map containers = new HashMap();

	static final List managerListeners = new ArrayList();

	private static IContainerFactory instance = null;

	private volatile static boolean init = false;

	static {
		instance = new ContainerFactory();
	}

	class ContainerEntry {
		private final IContainer container;
		private final ContainerTypeDescription typeDescription;

		public ContainerEntry(IContainer container, ContainerTypeDescription typeDescription) {
			this.container = container;
			this.typeDescription = typeDescription;
		}

		public IContainer getContainer() {
			return this.container;
		}

		public ContainerTypeDescription getContainerTypeDescription() {
			return this.typeDescription;
		}
	}

	public synchronized static IContainerFactory getDefault() {
		if (init == false) {
			// first mark the extension initalized because it initializeExtensions()
			// eventually calls this method again
			init = true;
			ECFPlugin.getDefault().initializeExtensions();
		}
		return instance;
	}

	protected ContainerFactory() {
		ECFPlugin.getDefault().addDisposable(new IDisposable() {
			public void dispose() {
				synchronized (containers) {
					for (Iterator i = containers.keySet().iterator(); i.hasNext();) {
						ContainerEntry entry = (ContainerEntry) containers.get(i.next());
						if (entry != null) {
							IContainer c = entry.getContainer();
							try {
								c.dispose();
							} catch (Throwable e) {
								// Log exception
								ECFPlugin.getDefault().log(new Status(IStatus.ERROR, ECFPlugin.getDefault().getBundle().getSymbolicName(), IStatus.ERROR, "container dispose error", e)); //$NON-NLS-1$
								Trace.catching(ECFPlugin.PLUGIN_ID, ECFDebugOptions.EXCEPTIONS_CATCHING, ContainerFactory.class, "doDispose", e); //$NON-NLS-1$
							}
						}
					}
					containers.clear();
				}
				containerdescriptions.clear();
				managerListeners.clear();
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#addDescription(org.eclipse.ecf.core.ContainerTypeDescription)
	 */
	public ContainerTypeDescription addDescription(ContainerTypeDescription containerTypeDescription) {
		return addDescription0(containerTypeDescription);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#getDescriptions()
	 */
	public List getDescriptions() {
		return getDescriptions0();
	}

	protected List getDescriptions0() {
		synchronized (containerdescriptions) {
			return new ArrayList(containerdescriptions.values());
		}
	}

	protected ContainerTypeDescription addDescription0(ContainerTypeDescription containerTypeDescription) {
		if (containerTypeDescription == null)
			return null;
		synchronized (containerdescriptions) {
			return (ContainerTypeDescription) containerdescriptions.put(containerTypeDescription.getName(), containerTypeDescription);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#containsDescription(org.eclipse.ecf.core.ContainerTypeDescription)
	 */
	public boolean containsDescription(ContainerTypeDescription containerTypeDescription) {
		return containsDescription0(containerTypeDescription);
	}

	protected boolean containsDescription0(ContainerTypeDescription containerTypeDescription) {
		if (containerTypeDescription == null)
			return false;
		synchronized (containerdescriptions) {
			return containerdescriptions.containsKey(containerTypeDescription.getName());
		}
	}

	protected ContainerTypeDescription getDescription0(ContainerTypeDescription containerTypeDescription) {
		if (containerTypeDescription == null)
			return null;
		synchronized (containerdescriptions) {
			return (ContainerTypeDescription) containerdescriptions.get(containerTypeDescription.getName());
		}
	}

	protected ContainerTypeDescription getDescription0(String containerTypeDescriptionName) {
		if (containerTypeDescriptionName == null)
			return null;
		synchronized (containerdescriptions) {
			return (ContainerTypeDescription) containerdescriptions.get(containerTypeDescriptionName);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#getDescriptionByName(java.lang.String)
	 */
	public ContainerTypeDescription getDescriptionByName(String containerTypeDescriptionName) {
		return getDescription0(containerTypeDescriptionName);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#removeDescription(org.eclipse.ecf.core.ContainerTypeDescription)
	 */
	public ContainerTypeDescription removeDescription(ContainerTypeDescription containerTypeDescription) {
		return removeDescription0(containerTypeDescription);

	}

	protected ContainerTypeDescription removeDescription0(ContainerTypeDescription containerTypeDescription) {
		if (containerTypeDescription == null)
			return null;
		synchronized (containerdescriptions) {
			return (ContainerTypeDescription) containerdescriptions.remove(containerTypeDescription.getName());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#getDescriptionsForContainerAdapter(java.lang.Class)
	 */
	public ContainerTypeDescription[] getDescriptionsForContainerAdapter(Class containerAdapter) {
		if (containerAdapter == null)
			throw new NullPointerException("containerAdapter cannot be null"); //$NON-NLS-1$
		List result = new ArrayList();
		List descriptions = getDescriptions();
		for (Iterator i = descriptions.iterator(); i.hasNext();) {
			ContainerTypeDescription description = (ContainerTypeDescription) i.next();
			String[] supportedAdapters = description.getSupportedAdapterTypes();
			if (supportedAdapters != null) {
				for (int j = 0; j < supportedAdapters.length; j++) {
					if (supportedAdapters[j].equals(containerAdapter.getName()))
						result.add(description);
				}
			}
		}
		return (ContainerTypeDescription[]) result.toArray(new ContainerTypeDescription[] {});
	}

	protected void throwContainerCreateException(String message, Throwable cause, String method) throws ContainerCreateException {
		ContainerCreateException except = (cause == null) ? new ContainerCreateException(message) : new ContainerCreateException(message, cause);
		Trace.throwing(ECFPlugin.PLUGIN_ID, ECFDebugOptions.EXCEPTIONS_THROWING, ContainerFactory.class, method, except);
		throw except;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer()
	 */
	public IContainer createContainer() throws ContainerCreateException {
		return createContainer(BASE_CONTAINER_NAME);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(org.eclipse.ecf.core.identity.ID)
	 */
	public IContainer createContainer(ID containerID) throws ContainerCreateException {
		if (containerID == null)
			return createContainer();
		return createContainer(BASE_CONTAINER_NAME, new Object[] {containerID});
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(org.eclipse.ecf.core.ContainerTypeDescription)
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription) throws ContainerCreateException {
		return createContainer(containerTypeDescription, (Object[]) null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(java.lang.String)
	 */
	public IContainer createContainer(String containerTypeDescriptionName) throws ContainerCreateException {
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), (Object[]) null);
	}

	private ContainerTypeDescription getDescriptionByNameWithException(String containerTypeDescriptionName) throws ContainerCreateException {
		ContainerTypeDescription typeDescription = getDescriptionByName(containerTypeDescriptionName);
		if (typeDescription == null)
			throw new ContainerCreateException("Container type description with name=" + containerTypeDescriptionName + " not found.  This may indicate that the desired provider is not available or not startable within runtime."); //$NON-NLS-1$ //$NON-NLS-2$
		return typeDescription;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(org.eclipse.ecf.core.ContainerTypeDescription,
	 *      java.lang.Object[])
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, Object[] parameters) throws ContainerCreateException {
		String method = "createContainer"; //$NON-NLS-1$
		Trace.entering(ECFPlugin.PLUGIN_ID, ECFDebugOptions.METHODS_ENTERING, ContainerFactory.class, method, new Object[] {containerTypeDescription, Trace.getArgumentsString(parameters)});
		if (containerTypeDescription == null)
			throwContainerCreateException("ContainerTypeDescription cannot be null", null, method); //$NON-NLS-1$
		ContainerTypeDescription cd = getDescription0(containerTypeDescription);
		if (cd == null)
			throwContainerCreateException("ContainerTypeDescription '" //$NON-NLS-1$
					+ containerTypeDescription.getName() + "' not found", null, method); //$NON-NLS-1$
		IContainerInstantiator instantiator = null;
		try {
			instantiator = cd.getInstantiator();
		} catch (Exception e) {
			throwContainerCreateException("createContainer cannot get IContainerInstantiator for description : " //$NON-NLS-1$
					+ containerTypeDescription, e, method);
		}
		// Ask instantiator to actually create instance
		IContainer container = instantiator.createInstance(containerTypeDescription, parameters);
		if (container == null)
			throwContainerCreateException("Instantiator returned null for '" //$NON-NLS-1$
					+ cd.getName() + "'", null, method); //$NON-NLS-1$
		// Add to containers map if container.getID() provides a valid value.
		ID containerID = container.getID();
		if (containerID != null)
			addContainer(container, cd);
		Trace.exiting(ECFPlugin.PLUGIN_ID, ECFDebugOptions.METHODS_EXITING, ContainerFactory.class, method, container);
		return container;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(java.lang.String,
	 *      java.lang.Object[])
	 */
	public IContainer createContainer(String containerTypeDescriptionName, Object[] parameters) throws ContainerCreateException {
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), parameters);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(org.eclipse.ecf.core.ContainerTypeDescription, org.eclipse.ecf.core.identity.ID, java.lang.Object[])
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, ID containerID, Object[] parameters) throws ContainerCreateException {
		if (containerID == null)
			return createContainer(containerTypeDescription, parameters);
		Object[] params = (parameters == null || parameters.length == 0) ? new Object[1] : new Object[parameters.length + 1];
		params[0] = containerID;
		if (parameters != null && parameters.length != 0)
			System.arraycopy(parameters, 0, params, 1, parameters.length);
		return createContainer(containerTypeDescription, params);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(java.lang.String, org.eclipse.ecf.core.identity.ID, java.lang.Object[])
	 */
	public IContainer createContainer(String containerTypeDescriptionName, ID containerID, Object[] parameters) throws ContainerCreateException {
		if (containerID == null)
			return createContainer(containerTypeDescriptionName, parameters);
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), containerID, parameters);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(org.eclipse.ecf.core.ContainerTypeDescription, org.eclipse.ecf.core.identity.ID)
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, ID containerID) throws ContainerCreateException {
		if (containerID == null)
			return createContainer(containerTypeDescription);
		return createContainer(containerTypeDescription, new Object[] {containerID});
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerFactory#createContainer(java.lang.String, org.eclipse.ecf.core.identity.ID)
	 */
	public IContainer createContainer(String containerTypeDescriptionName, ID containerID) throws ContainerCreateException {
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), new Object[] {containerID});
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(String containerTypeDescriptionName, String containerId) throws ContainerCreateException {
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), containerId);
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(String containerTypeDescriptionName, String containerId, Object[] parameters) throws ContainerCreateException {
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), containerId, parameters);
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, String containerId) throws ContainerCreateException {
		return createContainer(containerTypeDescription, containerId, (Object[]) null);
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, String containerId, Object[] parameters) throws ContainerCreateException {
		if (containerId == null)
			return createContainer(containerTypeDescription, parameters);
		Object[] params = (parameters == null || parameters.length == 0) ? new Object[1] : new Object[parameters.length + 1];
		params[0] = containerId;
		if (parameters != null && parameters.length != 0)
			System.arraycopy(parameters, 0, params, 1, parameters.length);
		return createContainer(containerTypeDescription, params);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerManager#getAllContainers()
	 */
	public IContainer[] getAllContainers() {
		List containerValues = new ArrayList();
		synchronized (containers) {
			Collection containerEntrys = containers.values();
			for (Iterator i = containerEntrys.iterator(); i.hasNext();) {
				ContainerEntry entry = (ContainerEntry) i.next();
				containerValues.add(entry.getContainer());
			}
		}
		return (IContainer[]) containerValues.toArray(new IContainer[] {});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerManager#getContainer(org.eclipse.ecf.core.identity.ID)
	 */
	public IContainer getContainer(ID containerID) {
		if (containerID == null)
			return null;
		synchronized (containers) {
			ContainerEntry entry = (ContainerEntry) containers.get(containerID);
			if (entry == null)
				return null;
			return entry.getContainer();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerManager#hasContainer(org.eclipse.ecf.core.identity.ID)
	 */
	public boolean hasContainer(ID containerID) {
		Assert.isNotNull(containerID);
		synchronized (containers) {
			return containers.containsKey(containerID);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerManager#addListener(org.eclipse.ecf.core.IContainerManagerListener)
	 */
	public boolean addListener(IContainerManagerListener listener) {
		Assert.isNotNull(listener);
		synchronized (managerListeners) {
			return managerListeners.add(listener);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.core.IContainerManager#removeListener(org.eclipse.ecf.core.IContainerManagerListener)
	 */
	public boolean removeListener(IContainerManagerListener listener) {
		Assert.isNotNull(listener);
		synchronized (managerListeners) {
			return managerListeners.remove(listener);
		}
	}

	public IContainer addContainer(IContainer container, ContainerTypeDescription typeDescription) {
		Assert.isNotNull(container);
		Assert.isNotNull(typeDescription);
		ID containerID = container.getID();
		Assert.isNotNull(containerID, "Container ID cannot be null"); //$NON-NLS-1$
		ContainerEntry result = null;
		synchronized (containers) {
			result = (ContainerEntry) containers.put(containerID, new ContainerEntry(container, typeDescription));
		}
		if (result == null)
			fireContainerAdded(container);
		return container;
	}

	/**
	 * @param result
	 */
	private void fireContainerAdded(IContainer result) {
		List toNotify = null;
		synchronized (managerListeners) {
			toNotify = new ArrayList(managerListeners);
		}
		for (Iterator i = toNotify.iterator(); i.hasNext();) {
			IContainerManagerListener cml = (IContainerManagerListener) i.next();
			cml.containerAdded(result);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.core.IContainerManager#removeContainer(org.eclipse.ecf.core.IContainer)
	 */
	public IContainer removeContainer(IContainer container) {
		Assert.isNotNull(container);
		ID containerID = container.getID();
		if (containerID == null)
			return null;
		return removeContainer(containerID);
	}

	public IContainer removeContainer(ID containerID) {
		Assert.isNotNull(containerID);
		ContainerEntry result = null;
		synchronized (containers) {
			result = (ContainerEntry) containers.remove(containerID);
		}
		IContainer resultContainer = null;
		if (result != null) {
			resultContainer = result.getContainer();
			fireContainerRemoved(resultContainer);
		}
		return resultContainer;
	}

	/**
	 * @param result
	 */
	private void fireContainerRemoved(IContainer result) {
		List toNotify = null;
		synchronized (managerListeners) {
			toNotify = new ArrayList(managerListeners);
		}
		for (Iterator i = toNotify.iterator(); i.hasNext();) {
			IContainerManagerListener cml = (IContainerManagerListener) i.next();
			cml.containerRemoved(result);
		}
	}

	public ContainerTypeDescription getContainerTypeDescription(ID containerID) {
		if (containerID == null)
			return null;
		synchronized (containers) {
			ContainerEntry entry = (ContainerEntry) containers.get(containerID);
			if (entry == null)
				return null;
			return entry.getContainerTypeDescription();
		}
	}

	public IContainerFactory getContainerFactory() {
		return this;
	}

	public void removeAllContainers() {
		synchronized (containers) {
			for (Iterator i = containers.keySet().iterator(); i.hasNext();) {
				ID key = (ID) i.next();
				ContainerEntry entry = (ContainerEntry) containers.get(key);
				i.remove();
				fireContainerRemoved(entry.getContainer());
			}
		}
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, ID containerID, Map parameters) throws ContainerCreateException {
		if (containerID == null)
			return createContainer(containerTypeDescription, parameters);
		if (parameters == null)
			return createContainer(containerTypeDescription, containerID);
		return createContainer(containerTypeDescription, new Object[] {containerID, parameters});
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, String containerId, Map parameters) throws ContainerCreateException {
		if (containerId == null)
			return createContainer(containerTypeDescription, parameters);
		if (parameters == null)
			return createContainer(containerTypeDescription, containerId);
		return createContainer(containerTypeDescription, new Object[] {containerId, parameters});
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(String containerTypeDescriptionName, ID containerID, Map parameters) throws ContainerCreateException {
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), containerID, parameters);
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(String containerTypeDescriptionName, String containerId, Map parameters) throws ContainerCreateException {
		return createContainer(getDescriptionByNameWithException(containerTypeDescriptionName), containerId, parameters);
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(ContainerTypeDescription containerTypeDescription, Map parameters) throws ContainerCreateException {
		if (parameters == null)
			return createContainer(containerTypeDescription);
		return createContainer(containerTypeDescription, new Object[] {parameters});
	}

	/**
	 * @since 3.1
	 */
	public IContainer createContainer(String containerTypeDescriptionName, Map parameters) throws ContainerCreateException {
		if (parameters == null)
			return createContainer(containerTypeDescriptionName);
		return createContainer(containerTypeDescriptionName, new Object[] {parameters});
	}

}